package md.frolov.bible.views;

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import md.frolov.bible.commands.GoToURLCommand;
import md.frolov.bible.editors.BibleEditorInput;
import md.frolov.bible.editors.MainEditor;
import md.frolov.bible.initializers.CustomXMLParser;
import md.frolov.bible.initializers.Utils;
import md.frolov.bible.model.Verse;

import org.eclipse.jface.resource.StringConverter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;

public class SearchView extends ViewPart {
	public final static String ID = "md.frolov.bible.views.SearchView";
	
	private Text text;
	private StyledText styledText;
	
	private StyleRange linkStyle;
	private StyleRange highlightStyle;
	private StyleRange commentStyle;
	private StyleRange counterStyle;
	
	private List<String> links = new ArrayList<String>();
	private List<StyleRange> styles = new ArrayList<StyleRange>();
	private List<Integer> ranges = new ArrayList<Integer>();
	
	private Cursor handCursor;
	private Cursor beamCursor;
	
	public SearchView() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public void createPartControl(Composite parent) {
		GridLayout gl = new GridLayout(1,false);
		
		parent.setLayout(gl);

		text = new Text(parent, SWT.BORDER);
		text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		
		text.addKeyListener(new KeyListener(){
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.keyCode == 13){
					search(text.getText());
				}
			}

			@Override
			public void keyReleased(KeyEvent e) {
				
			}
			
		});
		
		styledText = new StyledText(parent, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL);
		styledText.setLayoutData(new GridData(GridData.FILL_BOTH));

		hookListeners();
		
		handCursor = new Cursor(getSite().getShell().getDisplay(),SWT.CURSOR_HAND);
		beamCursor = new Cursor(getSite().getShell().getDisplay(),SWT.CURSOR_IBEAM);
		
		linkStyle = new StyleRange(0,0,new Color(styledText.getDisplay(),25,0,100),styledText.getBackground());
		linkStyle.fontStyle = SWT.BOLD;
		
		commentStyle = new StyleRange(0,0,new Color(styledText.getDisplay(),200,200,200),styledText.getBackground());
		
		highlightStyle = new StyleRange(0,0,new Color(styledText.getDisplay(),200,100,50),styledText.getBackground());
		
		counterStyle = new StyleRange(0,0,new Color(styledText.getDisplay(),200,200,200),styledText.getBackground());
		FontData fd = styledText.getFont().getFontData()[0];
		Font font = new Font(styledText.getDisplay(),fd.name,Math.round((float)(fd.height*0.8)), SWT.BOLD);
		counterStyle.font = font;
	}

	@Override
	public void setFocus() {
		text.setSelection(0,text.getText().length());
		text.setFocus();
	}
	
	private void clear(){
		styledText.setText("");
		links.clear();
		ranges.clear();
		styles.clear();
	}
	
	public void search(String str){
		styledText.setText("");
		if(str==null) return;
		if(str.equals("")) return;
		
		styledText.setText("Searching...");
		styledText.update();
		
		clear();
		
		long time = System.currentTimeMillis();
		Pattern idPattern = Pattern.compile("id=\"(.+?)\"");
		Pattern lidPattern = Pattern.compile("lid=\"(.+?)\"");
		
		StringBuilder sb = new StringBuilder();
//		sb.append("Query: \"").append(str).append('"');
//		sb.appendCodePoint(Character.LINE_SEPARATOR).appendCodePoint(Character.LINE_SEPARATOR);
		
		String contents = getContents();
//		String loweredContents = contents.toLowerCase();
		appendText(sb,"File parsed in: "+(System.currentTimeMillis()-time)/1000.0+"s",commentStyle);
		sb.appendCodePoint(Character.LINE_SEPARATOR);

		String[] words = str.split(" ");
		String temp;
		
		for(int i = 0; i<words.length;i++)
			for(int j = 0; j<i;j++)
				if(words[i].length()>words[j].length()){
					temp = words[i];
					words[i] =words[j];
					words[j] = temp;
				}
		
		System.out.println("searchign words: ");
		for (int i = 0; i < words.length; i++) {
			System.out.println(words[i]);
		}
		System.out.println();

		Matcher[] ms = new Matcher[words.length];
		for (int i = 0; i < words.length; i++) {
			ms[i] = Pattern.compile(words[i],Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE).matcher(contents);
		}
		
		int offset = 0;
		int vCounter = 0;

		Matcher m = idPattern.matcher(contents);
		Matcher m2 = lidPattern.matcher(contents);
		File file = getFile();

		
		search:
			while(ms[0].find(offset)){
				offset = ms[0].start();
				int vBegin = contents.lastIndexOf(">", offset);
				int vEnd = contents.indexOf("</v>",offset);
				
				for(int i = 1;i<ms.length;i++){
					if(!ms[i].find(vBegin)) break search;
					int tOffset = ms[i].start();
					if(tOffset>vEnd){
						offset = contents.lastIndexOf(">",tOffset);
						continue search;
					}
					
				}
				
				int vOffset = contents.lastIndexOf("<v ", vBegin);
				int chptOffset = contents.lastIndexOf("<chapter ",vOffset);
				offset = vEnd+1;
				
				m.find(chptOffset);
				String chapterId = m.group(1);
				m.find(vOffset);
				String verseId = m.group(1);
				m2.find(vOffset);
				String lId = m2.group(1);
				
				StringBuilder cont = new StringBuilder();
				cont.append(contents, vBegin+1,vEnd);
				Verse v = Utils.getVerse(file, verseId, lId, chapterId, cont.toString(), true);
			
				vCounter++;
//				sb.append(vCounter).append(". ");
				appendText(sb, vCounter+". ", counterStyle);
				appendText(sb, v.getName(), linkStyle, v.getUrl());
				sb.append(' ').append(v.getContents());
				sb.appendCodePoint(Character.LINE_SEPARATOR).appendCodePoint(Character.LINE_SEPARATOR);
				if(vCounter == 1000){
					sb.append("Too many results...").appendCodePoint(Character.LINE_SEPARATOR);
					break search;
				}
			}
		
//		search:
//		while((offset=loweredContents.indexOf(words[0], offset+1))!=-1){
//			int vBegin = contents.lastIndexOf(">", offset);
//			int vEnd = contents.indexOf("</v>",offset);
//			
//			for(int i = 1;i<words.length;i++){
//				int tOffset = loweredContents.indexOf(words[i],vBegin);
//				if(tOffset>vEnd){
//					offset = contents.lastIndexOf("</v>",tOffset);
//					continue search;
//				}
//				if(tOffset==-1) break search;
//			}
//			
//			int vOffset = contents.lastIndexOf("<v ", vBegin);
//			int chptOffset = contents.lastIndexOf("<chapter ",vOffset);
//			
//			m.find(chptOffset);
//			String chapterId = m.group(1);
//			m.find(vOffset);
//			String verseId = m.group(1);
//			m2.find(vOffset);
//			String lId = m2.group(1);
//			
//			StringBuilder cont = new StringBuilder();
//			cont.append(contents, vBegin+1,vEnd);
//			Verse v = Utils.getVerse(file, verseId, lId, chapterId, cont.toString(), true);
//			
//			sb.append(v.getName()).append(' ').append(v.getContents());
////			sb.append(contents,vBegin+1,vEnd);
////			sb.append(chptOffset).append(",").append(vOffset).append("-").append(endVOffset);
//			sb.appendCodePoint(Character.LINE_SEPARATOR).appendCodePoint(Character.LINE_SEPARATOR);
//		}
		
//		Matcher m = p.matcher(contents);
//		while(m.find()){
////			for(int i = 1; i<=m.groupCount();i++){
////				sb.append(i).append(": ");//.append(m.group(i));
////				sb.append(contents, m.start(i), m.end(i));
////				sb.appendCodePoint(Character.LINE_SEPARATOR);
////			}
//
//			sb.append(contents, m.start(), m.end());
//			
//			sb.appendCodePoint(Character.LINE_SEPARATOR).appendCodePoint(Character.LINE_SEPARATOR);
//		}

		long time3 = System.currentTimeMillis();
		for(int i=0;i<ms.length;i++){
			ms[i].reset(sb);
			while(ms[i].find()){
				appendStyle(highlightStyle, ms[i].start(), ms[i].end());
			}
		}
		
		appendText(sb, "Total verses: "+vCounter, commentStyle);
		sb.appendCodePoint(Character.LINE_SEPARATOR);
		appendText(sb,"Styled in: "+(System.currentTimeMillis()-time3)/1000.0+"s",commentStyle);
		sb.appendCodePoint(Character.LINE_SEPARATOR);

		
		appendText(sb, "Total time: "+(System.currentTimeMillis()-time)/1000.0+"s", commentStyle);

		setStyledTextContents(sb);
	}
	
	private void sortStyleRanges(){
		for(int i=0;i<styles.size();i++)
			for(int j=i;j<styles.size();j++)
				if(ranges.get(i*2)>ranges.get(j*2))
				{	
					swap(links,i,j);
					swap(ranges,i*2,j*2);
					swap(ranges,i*2+1,j*2+1);
					swap(styles,i,j);
				}
	}
	
	private void swap(List l, int i, int j){
		Object t = l.get(i);
		l.set(i, l.get(j));
		l.set(j, t);
	}
	
	private File getFile(){
		IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
		if(editor == null) return null;
		if(!(editor instanceof MainEditor)) return null;
		return ((BibleEditorInput)((MainEditor)editor).getEditorInput()).getChapter().getBook().getModule().getFile();
	}
	
	private String getContents(){
		try{
			FileInputStream fis = new FileInputStream(getFile());
			FileChannel fc = fis.getChannel();
			String retval = null;
			ByteBuffer bb = ByteBuffer.allocate((int)fc.size());
			fc.read(bb);
	
			bb.flip();
			retval = new String(bb.array());
			bb = null;
	
			return retval;

		} catch (Exception e){
			return "";
		}
	}
	
	private void setStyledTextContents(StringBuilder sb){
		styledText.setText(sb.toString());
		
		sortStyleRanges();
		
		int[] r = new int[ranges.size()];
		StyleRange[] sr = new StyleRange[styles.size()];
		for(int i=0;i<sr.length;i++){
			sr[i] = styles.get(i);
			r[i*2] = ranges.get(i*2);
			r[i*2+1] = ranges.get(i*2+1);
		}
		
		styledText.setStyleRanges(r, sr);
	}
	
	private void appendText(StringBuilder sb, String text,StyleRange style){
		appendText(sb, text, style, null);
	}
	
	private void appendText(StringBuilder sb, String text, StyleRange style, String ref){
		ranges.add(sb.length());
		styles.add(style);
		ranges.add(text.length());
		links.add(ref);
		sb.append(text);
	}
	
	private void appendStyle(StyleRange sr, int start, int end){
		ranges.add(start);
		styles.add(sr);
		ranges.add(end-start);
		links.add(null);
	}

	private void hookListeners(){
		styledText.addMouseListener(new MouseListener(){

			@Override
			public void mouseDown(MouseEvent e) {
				int offset;
				try{
					offset = styledText.getOffsetAtLocation(new Point(e.x,e.y));
				} catch (IllegalArgumentException ex){
					return;
				}
				
				//moving cursor at the beginning of hyperlink
				int i = getRangeAtOffset(offset);
				String ref = links.get(i);
				if(ref!=null){
					String url = ref;
					if(url==null) return;
					styledText.setSelection(ranges.get(i*2));
					GoToURLCommand.executeStatic(url);
				}
			}

			@Override
			public void mouseUp(MouseEvent e) {

			}

			@Override
			public void mouseDoubleClick(MouseEvent e) {
				// TODO Auto-generated method stub
				
			}
		});
		
		styledText.addMouseMoveListener(new MouseMoveListener(){

			@Override
			public void mouseMove(MouseEvent e) {
				int offset;
				try{
					offset = styledText.getOffsetAtLocation(new Point(e.x,e.y));
				} catch (IllegalArgumentException ex){
					styledText.setCursor(beamCursor);
					return;
				}
				
				if(getRefAtOffset(offset)!=null){
					styledText.setCursor(handCursor);
				} else {
					styledText.setCursor(beamCursor);
				}
			}
			
		});
	}

	private int getRangeAtOffset(int offset){
		for(int i=0;i<styles.size();i++){
			if(offset>=ranges.get(i*2) && offset<ranges.get(i*2)+ranges.get(i*2+1)){
				return i;
			}
			if(offset<ranges.get(i*2)) break;
		}
		return -1;
	}
	
	private String getRefAtOffset(int offset){
		int ix = getRangeAtOffset(offset);
		if(ix==-1) return null;
		return links.get(ix);
	}
}
